/*++

Module Name:

    device.c - Device handling events for example driver.

Abstract:

   This file contains the device entry points and callbacks.

Environment:

    Kernel-mode Driver Framework

--*/

#include "driver.h"

#include "device.tmh"

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, vpudrvCreateDevice)
#pragma alloc_text (PAGE, vpudrvPrepareDevice)
#pragma alloc_text (PAGE, vpudrvReleaseDevice)
#pragma alloc_text (PAGE, vpudrvInterruptCreate)
#pragma alloc_text (PAGE, vpudrvShutdownDevice)
#pragma alloc_text (PAGE, vpudrvCreateReservedMemory)
#pragma alloc_text (PAGE, vpudrvInterruptCreate)
#endif

NTSTATUS
vpudrvCreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
/*++

Routine Description:

    Worker routine called to create a device and its software resources.

Arguments:

    DeviceInit - Pointer to an opaque init structure. Memory for this
                    structure will be freed by the framework when the WdfDeviceCreate
                    succeeds. So don't access the structure after that point.

Return Value:

    NTSTATUS

--*/
{
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_VPU);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get a pointer to the device context structure that we just associated
        // with the device object. We define this structure in the device.h
        // header file. DeviceGetContext is an inline function generated by
        // using the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro in device.h.
        // This function will do the type checking and return the device context.
        // If you pass a wrong object handle it will return NULL and assert if
        // run under framework verifier mode.
        //
        deviceContext = DeviceGetContext(device);

        //
        // Initialize the context.
        //
        deviceContext->PrivateDeviceData = 0;
        deviceContext->Device = device;
        RtlZeroMemory(&deviceContext->instance_pool, sizeof(deviceContext->instance_pool));
        RtlZeroMemory(&deviceContext->reserved_video_memory, sizeof(deviceContext->reserved_video_memory));

        TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE,
            "     AddDevice PDO (0x%p) FDO (0x%p), DevExt (0x%p)",
            WdfDeviceWdmGetPhysicalDevice(device),
            WdfDeviceWdmGetDeviceObject(device), deviceContext);
        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_vpudrv,
            NULL // ReferenceString
            );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                "WdfDeviceCreateDeviceInterface failed: %!STATUS!", status);
            return status;
        }
        //
        // Initialize the I/O Package and any Queues
        //
        status = vpudrvQueueInitialize(device);
        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                "vpudrvQueueInitialize failed: %!STATUS!", status);
            return status;
        }

        status = vpudrvCreateReservedMemory(device);
        if (!NT_SUCCESS(status)) {
                 TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                     "vpudrvCreateReservedMemory failed: %!STATUS!", status);
        }
    }

    return status;
}

NTSTATUS
    vpudrvPrepareDevice(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourcesTranslated
    )
{
    ULONG i;
    BOOLEAN foundRegs = FALSE;
    ULONG regsLength = 0;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR  desc;
    PDEVICE_CONTEXT deviceContext;

    PAGED_CODE();

    deviceContext = DeviceGetContext(Device);

    //
    // Parse the resource list and save the resource information.
    //
    for (i=0; i < WdfCmResourceListGetCount(ResourcesTranslated); i++) {

        desc = WdfCmResourceListGetDescriptor(ResourcesTranslated, i );

        if(!desc) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "WdfResourceCmGetDescriptor failed");
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }

        switch (desc->Type) {

        case CmResourceTypeMemory:
            deviceContext->regsBasePA = desc->u.Memory.Start;
            regsLength = desc->u.Memory.Length;
            regsLength = HPI_REGISTER_SIZE;
            foundRegs = TRUE;
            TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, " - Memory Resource [%I64X-%I64X]",
                desc->u.Memory.Start.QuadPart, desc->u.Memory.Start.QuadPart + desc->u.Memory.Length);
            break;
        default:
            //
            // Ignore all other descriptors
            //
            TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE,
                " - Ignoreed   Resource [%08I64X-%08I64X] type=0x%x",
                desc->u.Port.Start.QuadPart,
                desc->u.Port.Start.QuadPart +
                desc->u.Port.Length,
                desc->Type);
            break;
        }
    }

    if (!foundRegs)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, " can not Find base address %08I64X, len=%d", deviceContext->regsBasePA.QuadPart, regsLength);
        return STATUS_DEVICE_CONFIGURATION_ERROR;
    }

    //
    // Map in the Registers Memory resource: BAR0
    //
    deviceContext->RegsBase = MmMapIoSpace(deviceContext->regsBasePA, regsLength, MmNonCached);

    if (!deviceContext->RegsBase) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            " - Unable to map Registers memory %08I64X, length %d",
            deviceContext->regsBasePA.QuadPart, regsLength);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    deviceContext->RegsLength = regsLength;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, " - Registers %I64X, length %d",  (LONGLONG)deviceContext->RegsBase, deviceContext->RegsLength );

    return STATUS_SUCCESS;
}

VOID
    vpudrvReleaseDevice(
    _In_ WDFDEVICE Device
    )
{
    PDEVICE_CONTEXT deviceContext;

    deviceContext = DeviceGetContext(Device);

    if (deviceContext->RegsBase) {
        MmUnmapIoSpace(deviceContext->RegsBase, deviceContext->RegsLength);
        deviceContext->RegsBase = NULL;
    }

    if (deviceContext->reserved_video_memory.base) {
        MmFreeContiguousMemory((PVOID)(ULONG_PTR)deviceContext->reserved_video_memory.base);
        deviceContext->reserved_video_memory.base = (ULONG_PTR)NULL;
    }
}

VOID
vpudrvShutdownDevice(
_In_ WDFDEVICE Device
)
/*++

Routine Description:

    Reset the device to put the device in a known initial state.
    This is called from D0Exit when the device is torn down or
    when the system is shutdown. Note that Wdf has already
    called out EvtDisable callback to disable the interrupt.

Arguments:

    DevExt -  Pointer to our adapter

Return Value:

    None

--*/
{
    UNREFERENCED_PARAMETER(Device);

    PAGED_CODE();

}

NTSTATUS
    vpudrvCreateReservedMemory(
    _In_ WDFDEVICE Device
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_CONTEXT deviceContext;
    PHYSICAL_ADDRESS LowestAcceptableAddress;
    PHYSICAL_ADDRESS HighestAcceptableAddress;
    PHYSICAL_ADDRESS BoundaryAddressMultiple;

    PAGED_CODE();

    deviceContext = DeviceGetContext(Device);

    LowestAcceptableAddress.QuadPart = 0;
    HighestAcceptableAddress.QuadPart = 0xFFFFFFFFFFFFFFFF;
    BoundaryAddressMultiple.QuadPart = 0x00;

    deviceContext->reserved_video_memory.size = VPUDRV_INIT_VIDEO_MEMORY_SIZE_IN_BYTE;
    deviceContext->reserved_video_memory.base = (ULONG_PTR)MmAllocateContiguousMemorySpecifyCache(deviceContext->reserved_video_memory.size,
        LowestAcceptableAddress,
        HighestAcceptableAddress,
        BoundaryAddressMultiple,
        MmWriteCombined);
    //deviceContext->VideoInitBufferBase = MmAllocateContiguousMemory(deviceContext->VideoInitBufferSize, HighestAcceptableAddress);
    if (!deviceContext->reserved_video_memory.base)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! MmAllocateContiguousMemorySpecifyCache failed: size=%d, %!STATUS!", deviceContext->reserved_video_memory.size, status);
        return status;
    }

    deviceContext->reserved_video_memory.phys_addr = MmGetPhysicalAddress((PVOID)(ULONG_PTR)deviceContext->reserved_video_memory.base);

    RtlZeroMemory((PVOID)(ULONG_PTR)deviceContext->reserved_video_memory.base, deviceContext->reserved_video_memory.size);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE,
        "%!FUNC! VideoInitBuffer %I64X  (#0x%I64X), length %I64d",
        deviceContext->reserved_video_memory.base,
        deviceContext->reserved_video_memory.phys_addr.QuadPart, deviceContext->reserved_video_memory.size);

    return status;
}

NTSTATUS
    vpudrvInterruptCreate(
    _In_  WDFDEVICE Device
    )
{
    NTSTATUS                    status;
    WDF_INTERRUPT_CONFIG        InterruptConfig;
    WDF_OBJECT_ATTRIBUTES       attributes;
    PDEVICE_CONTEXT deviceContext;

    PAGED_CODE();

    deviceContext = DeviceGetContext(Device);

    WDF_INTERRUPT_CONFIG_INIT(&InterruptConfig, vpudrvEvtInterruptIsr, vpudrvEvtInterruptDpc);

    InterruptConfig.EvtInterruptEnable  = vpudrvEvtInterruptEnable;
    InterruptConfig.EvtInterruptDisable = vpudrvEvtInterruptDisable;

    // JOHNR: Enable testing of the DpcForIsr Synchronization
    InterruptConfig.AutomaticSerialization = TRUE;

    //
    // Unlike WDM, framework driver should create interrupt object in EvtDeviceAdd and
    // let the framework do the resource parsing and registration of ISR with the kernel.
    // Framework connects the interrupt after invoking the EvtDeviceD0Entry callback
    // and disconnect before invoking EvtDeviceD0Exit. EvtInterruptEnable is called after
    // the interrupt interrupt is connected and EvtInterruptDisable before the interrupt is
    // disconnected.
    //
    status = WdfInterruptCreate(Device, &InterruptConfig, WDF_NO_OBJECT_ATTRIBUTES, &deviceContext->Interrupt);

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "WdfInterruptCreate failed: %!STATUS!", status);
    }

    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = deviceContext->Interrupt;

    status = WdfWaitLockCreate(&attributes, &deviceContext->InterruptWaitlock);

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "WdfWaitLockCreate failed: %!STATUS!", status);
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
    return status;

}

BOOLEAN
vpudrvEvtInterruptIsr(
    _In_ WDFINTERRUPT Interrupt,
    _In_ ULONG        MessageID
    )
/*++
Routine Description:

    Interrupt handler for this driver. Called at DIRQL level when the
    device or another device sharing the same interrupt line asserts
    the interrupt. The driver first checks the device to make sure whether
    this interrupt is generated by its device and if so clear the interrupt
    register to disable further generation of interrupts and queue a
    DPC to do other I/O work related to interrupt - such as reading
    the device memory, starting a DMA transaction, coping it to
    the request buffer and completing the request, etc.

Arguments:

    Interrupt   - Handle to WDFINTERRUPT Object for this device.
    MessageID  - MSI message ID (always 0 in this configuration)

Return Value:

     TRUE   --  This device generated the interrupt.
     FALSE  --  This device did not generated this interrupt.

--*/
{
    PDEVICE_CONTEXT deviceContext;
    BOOLEAN isRecognized = FALSE;

    UNREFERENCED_PARAMETER(MessageID);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "--> vpudrvEvtInterruptIsr");

    deviceContext = DeviceGetContext(WdfInterruptGetDevice(Interrupt));

#if 0
    isRecognized = ReadVpuRegister(BIT_INT_REASON);
    if (isRecognized)
    {
        WriteVpuRegister(BIT_INT_CLEAR, 0x1);

        //
        // Request the DPC to complete the transfer.
        //
        WdfInterruptQueueDpcForIsr(devExt->Interrupt );
    }
#endif

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "<-- vpudrvEvtInterruptIsr");

    return isRecognized;
}

VOID
vpudrvEvtInterruptDpc(
    _In_ WDFINTERRUPT Interrupt,
    _In_ WDFDEVICE    Device
    )
/*++

Routine Description:

    DPC callback for ISR. Please note that on a multiprocessor system,
    you could have more than one DPCs running simultaneously on
    multiple processors. So if you are accessing any global resources
    make sure to synchronize the accesses with a spinlock.

Arguments:

    Interrupt  - Handle to WDFINTERRUPT Object for this device.
    Device    - WDFDEVICE object passed to InterruptCreate

Return Value:

--*/
{
    PDEVICE_CONTEXT deviceContext;

    UNREFERENCED_PARAMETER(Device);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "--> vpudrvEvtInterruptDpc");

    deviceContext = DeviceGetContext(WdfInterruptGetDevice(Interrupt));
#if 0
    //
    // Acquire this device's InterruptSpinLock.
    //
    WdfInterruptAcquireLock( Interrupt );

    //
    // Release our interrupt spinlock
    //
    WdfInterruptReleaseLock( Interrupt );
#endif
    WdfWaitLockRelease(deviceContext->InterruptWaitlock);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "<-- vpudrvEvtInterruptDpc");

    return;
}

NTSTATUS
vpudrvEvtInterruptEnable(
    _In_ WDFINTERRUPT Interrupt,
    _In_ WDFDEVICE    Device
    )
/*++

Routine Description:

    Called by the framework at DIRQL immediately after registering the ISR with the kernel
    by calling IoConnectInterrupt.

Return Value:

    NTSTATUS
--*/
{
    PDEVICE_CONTEXT deviceContext;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE,
                "VPUEvtInterruptEnable: Interrupt 0x%p, Device 0x%p\n",
                Interrupt, Device);

    deviceContext = DeviceGetContext(WdfInterruptGetDevice(Interrupt));
    // It may be porting point; this whould include to enable bit for VPU in interrupt controller for SOC
    return STATUS_SUCCESS;
}

NTSTATUS
vpudrvEvtInterruptDisable(
    _In_ WDFINTERRUPT Interrupt,
    _In_ WDFDEVICE    Device
    )
/*++

Routine Description:

    Called by the framework at DIRQL before De registering the ISR with the kernel
    by calling IoDisconnectInterrupt.

Return Value:

    NTSTATUS
--*/
{
    PDEVICE_CONTEXT deviceContext;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE,
                "vpudrvEvtInterruptDisable: Interrupt 0x%p, Device 0x%p\n",
                Interrupt, Device);

    deviceContext = DeviceGetContext(WdfInterruptGetDevice(Interrupt));
    // It may be porting point; this whould include to disable bit for VPU in interrupt controller for SOC
    return STATUS_SUCCESS;
}

